/*
 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.org.apache.xalan.internal.utils;

import com.sun.org.apache.xalan.internal.XalanConstants;


/**
 * This class is not the same as that in Xerces. It is used to manage the
 * state of corresponding Xerces properties and pass the values over to
 * the Xerces Security Manager.
 *
 * @author Joe Wang Oracle Corp.
 */
public final class XMLSecurityManager {

  /**
   * States of the settings of a property, in the order: default value, value
   * set by FEATURE_SECURE_PROCESSING, jaxp.properties file, jaxp system
   * properties, and jaxp api properties
   */
  public static enum State {
    //this order reflects the overriding order

    DEFAULT("default"), FSP("FEATURE_SECURE_PROCESSING"),
    JAXPDOTPROPERTIES("jaxp.properties"), SYSTEMPROPERTY("system property"),
    APIPROPERTY("property");

    final String literal;

    State(String literal) {
      this.literal = literal;
    }

    String literal() {
      return literal;
    }
  }

  /**
   * Limits managed by the security manager
   */
  public static enum Limit {

    ENTITY_EXPANSION_LIMIT(XalanConstants.JDK_ENTITY_EXPANSION_LIMIT,
        XalanConstants.SP_ENTITY_EXPANSION_LIMIT, 0, 64000),
    MAX_OCCUR_NODE_LIMIT(XalanConstants.JDK_MAX_OCCUR_LIMIT,
        XalanConstants.SP_MAX_OCCUR_LIMIT, 0, 5000),
    ELEMENT_ATTRIBUTE_LIMIT(XalanConstants.JDK_ELEMENT_ATTRIBUTE_LIMIT,
        XalanConstants.SP_ELEMENT_ATTRIBUTE_LIMIT, 0, 10000),
    TOTAL_ENTITY_SIZE_LIMIT(XalanConstants.JDK_TOTAL_ENTITY_SIZE_LIMIT,
        XalanConstants.SP_TOTAL_ENTITY_SIZE_LIMIT, 0, 50000000),
    GENERAL_ENTITY_SIZE_LIMIT(XalanConstants.JDK_GENERAL_ENTITY_SIZE_LIMIT,
        XalanConstants.SP_GENERAL_ENTITY_SIZE_LIMIT, 0, 0),
    PARAMETER_ENTITY_SIZE_LIMIT(XalanConstants.JDK_PARAMETER_ENTITY_SIZE_LIMIT,
        XalanConstants.SP_PARAMETER_ENTITY_SIZE_LIMIT, 0, 1000000),
    MAX_ELEMENT_DEPTH_LIMIT(XalanConstants.JDK_MAX_ELEMENT_DEPTH,
        XalanConstants.SP_MAX_ELEMENT_DEPTH, 0, 0);

    final String apiProperty;
    final String systemProperty;
    final int defaultValue;
    final int secureValue;

    Limit(String apiProperty, String systemProperty, int value, int secureValue) {
      this.apiProperty = apiProperty;
      this.systemProperty = systemProperty;
      this.defaultValue = value;
      this.secureValue = secureValue;
    }

    public boolean equalsAPIPropertyName(String propertyName) {
      return (propertyName == null) ? false : apiProperty.equals(propertyName);
    }

    public boolean equalsSystemPropertyName(String propertyName) {
      return (propertyName == null) ? false : systemProperty.equals(propertyName);
    }

    public String apiProperty() {
      return apiProperty;
    }

    String systemProperty() {
      return systemProperty;
    }

    int defaultValue() {
      return defaultValue;
    }

    int secureValue() {
      return secureValue;
    }
  }

  /**
   * Map old property names with the new ones
   */
  public static enum NameMap {

    ENTITY_EXPANSION_LIMIT(XalanConstants.SP_ENTITY_EXPANSION_LIMIT,
        XalanConstants.ENTITY_EXPANSION_LIMIT),
    MAX_OCCUR_NODE_LIMIT(XalanConstants.SP_MAX_OCCUR_LIMIT,
        XalanConstants.MAX_OCCUR_LIMIT),
    ELEMENT_ATTRIBUTE_LIMIT(XalanConstants.SP_ELEMENT_ATTRIBUTE_LIMIT,
        XalanConstants.ELEMENT_ATTRIBUTE_LIMIT);
    final String newName;
    final String oldName;

    NameMap(String newName, String oldName) {
      this.newName = newName;
      this.oldName = oldName;
    }

    String getOldName(String newName) {
      if (newName.equals(this.newName)) {
        return oldName;
      }
      return null;
    }
  }

  /**
   * Values of the properties
   */
  private final int[] values;
  /**
   * States of the settings for each property
   */
  private State[] states;
  /**
   * States that determine if properties are set explicitly
   */
  private boolean[] isSet;


  /**
   * Index of the special entityCountInfo property
   */
  private int indexEntityCountInfo = 10000;
  private String printEntityCountInfo = "";

  /**
   * Default constructor. Establishes default values for known security
   * vulnerabilities.
   */
  public XMLSecurityManager() {
    this(false);
  }

  /**
   * Instantiate Security Manager in accordance with the status of
   * secure processing
   */
  public XMLSecurityManager(boolean secureProcessing) {
    values = new int[Limit.values().length];
    states = new State[Limit.values().length];
    isSet = new boolean[Limit.values().length];
    for (Limit limit : Limit.values()) {
      if (secureProcessing) {
        values[limit.ordinal()] = limit.secureValue();
        states[limit.ordinal()] = State.FSP;
      } else {
        values[limit.ordinal()] = limit.defaultValue();
        states[limit.ordinal()] = State.DEFAULT;
      }
    }
    //read system properties or jaxp.properties
    readSystemProperties();
  }

  /**
   * Setting FEATURE_SECURE_PROCESSING explicitly
   */
  public void setSecureProcessing(boolean secure) {
    for (Limit limit : Limit.values()) {
      if (secure) {
        setLimit(limit.ordinal(), State.FSP, limit.secureValue());
      } else {
        setLimit(limit.ordinal(), State.FSP, limit.defaultValue());
      }
    }
  }

  /**
   * Set limit by property name and state
   *
   * @param propertyName property name
   * @param state the state of the property
   * @param value the value of the property
   * @return true if the property is managed by the security manager; false if otherwise.
   */
  public boolean setLimit(String propertyName, State state, Object value) {
    int index = getIndex(propertyName);
    if (index > -1) {
      setLimit(index, state, value);
      return true;
    }
    return false;
  }

  /**
   * Set the value for a specific limit.
   *
   * @param limit the limit
   * @param state the state of the property
   * @param value the value of the property
   */
  public void setLimit(Limit limit, State state, int value) {
    setLimit(limit.ordinal(), state, value);
  }

  /**
   * Set the value of a property by its index
   *
   * @param index the index of the property
   * @param state the state of the property
   * @param value the value of the property
   */
  public void setLimit(int index, State state, Object value) {
    if (index == indexEntityCountInfo) {
      //if it's explicitly set, it's treated as yes no matter the value
      printEntityCountInfo = (String) value;
    } else {
      int temp = 0;
      try {
        temp = Integer.parseInt((String) value);
        if (temp < 0) {
          temp = 0;
        }
      } catch (NumberFormatException e) {
      }
      setLimit(index, state, temp);
    }
  }

  /**
   * Set the value of a property by its index
   *
   * @param index the index of the property
   * @param state the state of the property
   * @param value the value of the property
   */
  public void setLimit(int index, State state, int value) {
    if (index == indexEntityCountInfo) {
      //if it's explicitly set, it's treated as yes no matter the value
      printEntityCountInfo = XalanConstants.JDK_YES;
    } else {
      //only update if it shall override
      if (state.compareTo(states[index]) >= 0) {
        values[index] = value;
        states[index] = state;
        isSet[index] = true;
      }
    }
  }


  /**
   * Return the value of the specified property.
   *
   * @param propertyName the property name
   * @return the value of the property as a string. If a property is managed by this manager, its
   * value shall not be null.
   */
  public String getLimitAsString(String propertyName) {
    int index = getIndex(propertyName);
    if (index > -1) {
      return getLimitValueByIndex(index);
    }

    return null;
  }

  /**
   * Return the value of a property by its ordinal
   *
   * @param limit the property
   * @return value of a property
   */
  public String getLimitValueAsString(Limit limit) {
    return Integer.toString(values[limit.ordinal()]);
  }

  /**
   * Return the value of the specified property
   *
   * @param limit the property
   * @return the value of the property
   */
  public int getLimit(Limit limit) {
    return values[limit.ordinal()];
  }

  /**
   * Return the value of a property by its ordinal
   *
   * @param index the index of a property
   * @return value of a property
   */
  public int getLimitByIndex(int index) {
    return values[index];
  }

  /**
   * Return the value of a property by its index
   *
   * @param index the index of a property
   * @return limit of a property as a string
   */
  public String getLimitValueByIndex(int index) {
    if (index == indexEntityCountInfo) {
      return printEntityCountInfo;
    }

    return Integer.toString(values[index]);
  }

  /**
   * Return the state of the limit property
   *
   * @param limit the limit
   * @return the state of the limit property
   */
  public State getState(Limit limit) {
    return states[limit.ordinal()];
  }

  /**
   * Return the state of the limit property
   *
   * @param limit the limit
   * @return the state of the limit property
   */
  public String getStateLiteral(Limit limit) {
    return states[limit.ordinal()].literal();
  }

  /**
   * Get the index by property name
   *
   * @param propertyName property name
   * @return the index of the property if found; return -1 if not
   */
  public int getIndex(String propertyName) {
    for (Limit limit : Limit.values()) {
      if (limit.equalsAPIPropertyName(propertyName)) {
        //internally, ordinal is used as index
        return limit.ordinal();
      }
    }
    //special property to return entity count info
    if (propertyName.equals(XalanConstants.JDK_ENTITY_COUNT_INFO)) {
      return indexEntityCountInfo;
    }
    return -1;
  }

  /**
   * Indicate if a property is set explicitly
   */
  public boolean isSet(int index) {
    return isSet[index];
  }

  public boolean printEntityCountInfo() {
    return printEntityCountInfo.equals(XalanConstants.JDK_YES);
  }

  /**
   * Read from system properties, or those in jaxp.properties
   */
  private void readSystemProperties() {

    for (Limit limit : Limit.values()) {
      if (!getSystemProperty(limit, limit.systemProperty())) {
        //if system property is not found, try the older form if any
        for (NameMap nameMap : NameMap.values()) {
          String oldName = nameMap.getOldName(limit.systemProperty());
          if (oldName != null) {
            getSystemProperty(limit, oldName);
          }
        }
      }
    }

  }

  /**
   * Read from system properties, or those in jaxp.properties
   *
   * @param property the type of the property
   * @param sysPropertyName the name of system property
   */
  private boolean getSystemProperty(Limit limit, String sysPropertyName) {
    try {
      String value = SecuritySupport.getSystemProperty(sysPropertyName);
      if (value != null && !value.equals("")) {
        values[limit.ordinal()] = Integer.parseInt(value);
        states[limit.ordinal()] = State.SYSTEMPROPERTY;
        return true;
      }

      value = SecuritySupport.readJAXPProperty(sysPropertyName);
      if (value != null && !value.equals("")) {
        values[limit.ordinal()] = Integer.parseInt(value);
        states[limit.ordinal()] = State.JAXPDOTPROPERTIES;
        return true;
      }
    } catch (NumberFormatException e) {
      //invalid setting
      throw new NumberFormatException(
          "Invalid setting for system property: " + limit.systemProperty());
    }
    return false;
  }
}
